package com.example.services.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.bean.copier.CopyOptions;
import cn.hutool.core.lang.UUID;
import cn.hutool.core.util.RandomUtil;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.example.dto.LoginFormDTO;
import com.example.dto.Result;
import com.example.dto.UserDTO;
import com.example.entity.User;
import com.example.mapper.UserMapper;
import com.example.services.IUserService;
import com.example.utils.RegexUtils;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import jakarta.servlet.http.HttpSession;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;

import static com.example.utils.RedisConstants.*;
import static com.example.utils.SystemConstants.USER_NICK_NAME_PREFIX;

/**
 * 服务实现类
 */
@Slf4j
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {

    /**
     * Token是后端只需要设置算法和密码，然后将Token发给前端（后端仅仅留存对应的算法和密码，其他进步保存）
     * 前端每次访问后端都会携带该Token，然后后端只需要根据之前设置的算法和密码就可以对Token解析
     * 如果可以解析成功和完成校验，则后端接受前端发送的请求
     */

    /**
     * Session是后端对Session进行设置，然后后端保存Session并且将Session_ID发送给前端，前端保存Session_ID
     * 前端每次访问后端都需要在Cookie中携带Session_ID，后端获取Session_ID之后，从后端的存储里面根据Session_ID查找对应的Session
     * 接着对Session进行校验，如果成功则接收前端发送的请求
     */

    /**
     * Token与Session的区别为：
     * Token在后端仅保存对应的算法和密码，然后给前端对应的Token即可
     * Session是将所有的Session保存在后端，然后给前端Session_ID
     */
    @Resource
    private StringRedisTemplate stringRedisTemplate;

    /**
     * 发送短信验证码
     *
     * @param phone
     * @param session
     * @return
     */
    @Override
    public Result sendCode(String phone, HttpSession session) {
        // 1.校验手机号
        if (RegexUtils.isPhoneInvalid(phone)) {
            // 2.如果不符合，返回错误信息
            return Result.fail("手机号格式错误！");
        }
        // 3.符合，生成验证码
        String code = RandomUtil.randomNumbers(6);
        /**
         // 4.保存验证码到session
         session.setAttribute("phone", phone);
         session.setAttribute("code", code);
         */
        // 4.保存验证码到redis
        stringRedisTemplate.opsForValue().set(LOGIN_CODE_KEY + phone, code, LOGIN_CODE_TTL, TimeUnit.MINUTES);
        // 5.发送验证码
        log.debug("发送短信验证码成功，验证码：{}", code);
        // 6.返回ok
        return Result.ok();
    }

    /**
     * 登录功能
     *
     * @param loginForm
     * @param session
     * @return
     */
    @Override
    public Result login(LoginFormDTO loginForm, HttpSession session) {
        // 1.校验手机号
        String phone = loginForm.getPhone();
        if (RegexUtils.isPhoneInvalid(phone)) {
            // 不符合，返回错误信息
            return Result.fail("手机号格式错误！");
        }
        /**
         * 发送短信验证码的手机号需要与登录功能的手机号相同
         */
//        String cachePhone = (String) session.getAttribute("phone");
//        String phone = loginForm.getPhone();
//        if (!cachePhone.equals(phone)) {
//            return Result.fail("手机号错误！");
//        }
        /**
         // 2.效验验证码
         Object cacheCode = session.getAttribute("code");
         */
        // 2.从redis获取验证码并校验
        String cacheCode = stringRedisTemplate.opsForValue().get(LOGIN_CODE_KEY + phone);
        String code = loginForm.getCode();
        if (!cacheCode.equals(code)) {
            // 3.不一致，报错
            return Result.fail("验证码错误！");
        }
        // 4.一致，根据手机号查询用户
        User user = query().eq("phone", phone).one();
        // 5.判断用户是否存在
        if (user == null) {
            // 6.不存在，创建新用户并保存
            user = createUserWithPhone(phone);
        }
        /**
         // 7.保存用户信息到session中
         UserDTO userDTO = BeanUtil.copyProperties(user, UserDTO.class);
         session.setAttribute("user", userDTO);
         */
        // 7.保存用户信息到redis
        // 7.1.随机生成token，作为登录令牌
        String token = UUID.randomUUID().toString(true);
        // 7.2.将User对象转为HashMap存储
        /**
         * 将登录成功的用户 user 实体对象，拷贝为一个 简化版的用户信息对象 UserDTO。
         * 这是为了避免暴露过多字段（比如密码、敏感信息）到前端或 Redis。
         */
        UserDTO userDTO = BeanUtil.copyProperties(user, UserDTO.class);
        Map<String, Object> userMap = BeanUtil.beanToMap(userDTO, new HashMap<>(),
                /**
                 * 链式调用（Fluent API） + Lambda 表达式
                 * 在创建一个 CopyOptions 对象时，通过链式调用配置选项，并用 lambda 表达式指定字段值转换规则。
                 */
                CopyOptions.
                        create().
                        setIgnoreNullValue(true). // 忽略 null 字段，避免 Redis 存空值
                        setFieldValueEditor((fieldName, fieldValue) -> fieldValue.toString())); // 所有字段值转换为字符串（Redis 中 Hash 类型只存字符串）
        // 7.3.存储
        String tokenKey = LOGIN_USER_KEY + token;
        /**
         * 把用户信息写入 Redis 的 Hash 结构 中。
         */
        stringRedisTemplate.opsForHash().putAll(tokenKey, userMap);
        // 7.4.设置token有效期
        stringRedisTemplate.expire(tokenKey, LOGIN_USER_TTL, TimeUnit.MINUTES);
        // 8.返回token
        return Result.ok(token);
    }

    private User createUserWithPhone(String phone) {
        // 1.创建用户
        User user = new User();
        user.setPhone(phone);
        user.setNickName(USER_NICK_NAME_PREFIX + RandomUtil.randomString(10));
        // 2.保存用户
        save(user);
        return user;
    }

}
